﻿using System;
using System.Collections.Generic;
using System.Web;
using Xenta.Utils;

namespace Xenta.Web
{
    /// <summary>
    /// Exposes the service context interface.
    /// </summary>
    public interface IServiceContext
    {
        #region Properties

        /// <summary>
        /// Gets the associated HTTP context.
        /// </summary>
        HttpContext HttpContext
        {
            get;
        }

        /// <summary>
        /// Gets the infrastructure name.
        /// </summary>
        string Infrastructure
        {
            get;
        }

        /// <summary>
        /// Gets the configuration associated with the context.
        /// </summary>
        /// <remarks>
        /// You may use this property to override global service configuration,
        /// e.g. change the default localization profile code. Return null, if no
        /// configuration should be added.
        /// </remarks>
        IDictionary<String, String> Config
        {
            get;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the context.
        /// </summary>
        /// <returns>The conversation identifier.</returns>
        Guid Initialize();

        /// <summary>
        /// Authenticates the context.
        /// </summary>
        /// <returns>The session identifier.</returns>
        Guid Authenticate();

        #endregion
    }

    /// <summary>
    /// Represents the service.
    /// </summary>
    public sealed class Service
    {
        #region Constants

        /// <summary>
        /// The string key, which is used to store 
        /// context in HttpContext items. 
        /// </summary>
        public const string CTX_KEY = "SERVICE";
        private readonly static object CTX_LOCK = new object();

        #endregion

        #region Fields

        private readonly IServiceContext _context;
        private readonly IDictionary<String, String> _config;
        private readonly IDictionary<String, IDictionary<String, String>> _languagePacks;

        #endregion

        #region Ctors

        private Service(IServiceContext ctx)
        {
            _context = ctx;
            _config = new Dictionary<String, String>();
            _languagePacks = new Dictionary<String, IDictionary<String, String>>();
        }

        #endregion
        
        #region Properties

        /// <summary>
        /// Gets the service context.
        /// </summary>
        public IServiceContext Context
        {
            get
            {
                return _context;
            }
        }

        /// <summary>
        /// Gets the conversation identifier.
        /// </summary>
        public Guid CID
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the session identifier.
        /// </summary>
        public Guid SID
        {
            get
            {
                return Session != null 
                    ? JDM.Guid(Session.Guid) 
                    : Guid.Empty;
            }
        }

        /// <summary>
        /// Gets the session associated with context.
        /// </summary>
        public dynamic Session
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets a value indicates whether the 
        /// context is authenticated.
        /// </summary>
        public bool IsAuthenticated
        {
            get
            {
                return User != null;
            }
        }

        /// <summary>
        /// Gets the user associated with context.
        /// </summary>
        public dynamic User
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the account associated with context.
        /// </summary>
        public dynamic Account
        {
            get
            {
                return IsAuthenticated ? User.Account : null;
            }
        }

        /// <summary>
        /// Gets the file owner associated with context.
        /// </summary>
        public dynamic FileOwner
        {
            get
            {
                return IsAuthenticated ? User.FileOwner : null;
            }
        }

        /// <summary>
        /// The publication author associated with context.
        /// </summary>
        public dynamic PublicationAuthor
        {
            get
            {
                return IsAuthenticated ? User.PublicationAuthor : null;
            }
        }

        /// <summary>
        /// Gets the catalog manager associated with context.
        /// </summary>
        public dynamic CatalogManager
        {
            get
            {
                return IsAuthenticated ? User.CatalogManager : null;
            }
        }

        /// <summary>
        /// Gets the customer associated with context.
        /// </summary>
        public dynamic Customer
        {
            get
            {
                return IsAuthenticated ? User.Customer : null;
            }
        }

        /// <summary>
        /// Gets the seller associated with context.
        /// </summary>
        public dynamic Seller
        {
            get
            {
                return IsAuthenticated ? User.Seller : null;
            }
        }

        /// <summary>
        /// Gets the forum member associated with context.
        /// </summary>
        public dynamic ForumMember
        {
            get
            {
                return IsAuthenticated ? User.ForumMember : null;
            }
        }

        /// <summary>
        /// Gets the application localization profile.
        /// </summary>
        public dynamic Localization
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the system localization profile.
        /// </summary>
        public dynamic SystemLocalization
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the default localization profile.
        /// </summary>
        public dynamic DefaultLocalization
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        public IDictionary<String, String> Config
        {
            get
            {
                return _config;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Instantiates a context.
        /// </summary>
        /// <param name="ctx">The service context.</param>
        /// <returns>The context instance.</returns>
        public static Service Instantiate(IServiceContext ctx)
        {
            if(ctx == null)
                throw new ArgumentNullException("ctx");
            var svc = new Service(ctx);
            ctx.HttpContext.Items[CTX_KEY] = svc;
            return svc;
        }

        /// <summary>
        /// Initializes the context.
        /// </summary>
        public void Initialize()
        {
            CID = Context.Initialize();
            foreach(var i in this.ProcessOperation("GetConfigParamDictionary").Items)
                Config[JDM.String(i.Key)] = JDM.String(i.Value);
            if(Context.Config != null)
            {
                foreach(var kv in Context.Config)
                    Config[kv.Key] = kv.Value;
            }
            SystemLocalization = this.ProcessOperation("GetLocalizationProfileByCode", new
            {
                Code = this.Config(Par.In("Core").Of("LocalizationProfileEntity").Name("System")),
                Fetch = "Entire"
            }, new
            {
                DisclosureLevel = "Entire"
            });
            DefaultLocalization = this.ProcessOperation("GetLocalizationProfileByCode", new
            {
                Code = this.Config(Par.In("Core").Of("LocalizationProfileEntity").Name("Default")),
                Fetch = "Entire"
            }, new
            {
                DisclosureLevel = "Entire"
            });
            Localization = DefaultLocalization;
        }

        /// <summary>
        /// Authenticates the context.
        /// </summary>
        public void Authenticate()
        {
            var sid = Context.Authenticate();
            if(sid == Guid.Empty)
                return;

            Session = this.ProcessOperation("ConnectToSession", new
            {
                Guid = sid
            }, new
            {
                DisclosureLevel = "Entire"
            });
            if(Session == null)
                return;

            User = this.ProcessOperation("GetAccountWithProfiles", new
            {
                EntityID = JDM.Int32(Session.Account.EntityID)
            }, new
            {
                DisclosureLevel = "Entire"
            });
            if(JDM.String(User.Account.LocalizationProfile.Code) != JDM.String(Localization.Code))
            {
                Localization = this.ProcessOperation("GetLocalizationProfileByCode", new
                {
                    Code = JDM.String(Account.LocalizationProfile.Code),
                    Fetch = "Entire"
                }, new
                {
                    DisclosureLevel = "Entire"
                });
            }
        }

        /// <summary>
        /// Gets the language pack.
        /// </summary>
        /// <param name="code">The language pack code.</param>
        /// <returns>The language pack dictionary.</returns>
        public IDictionary<String, String> LanguagePack(string code)
        {
            if(String.IsNullOrWhiteSpace(code))
                return null;
            LoadLanguagePack(code);
            if(!_languagePacks.ContainsKey(code))
                return null;
            return _languagePacks[code];
        }

        #endregion

        #region Utilities

        private void LoadLanguagePack(string code)
        {
            if(String.IsNullOrWhiteSpace(code) || _languagePacks.ContainsKey(code))
                return;

            var pack = this.ProcessOperation("GetLanguagePackByCode", new
            {
                LanguageID = JDM.Int32(Localization.Language.EntityID),
                Code = code
            }, new 
            {
                DisclosureLevel = "Base"
            });
            if(pack == null)
                return;

            lock(CTX_LOCK)
            {
                string cacheTimeKey = String.Format("{0}.LanguagePack[{1}].time-{2}", 
                    Context.Infrastructure, 
                    code,
                    JDM.String(pack.Language.Code));
                string cacheContentKey = String.Format("{0}.LanguagePack[{1}].content-{2}", 
                    Context.Infrastructure, 
                    code,
                    JDM.String(pack.Language.Code));
                var cacheTime = (DateTime?)Context.HttpContext.Cache[cacheTimeKey];
                var cacheContent = Context.HttpContext.Cache[cacheContentKey]
                    as Dictionary<String, String>;

                if(!cacheTime.HasValue || cacheContent == null ||
                    JDM.DateTime(pack.UpdatedOn).CompareTo(cacheTime.Value) > 0)
                {
                    var content = this.ProcessOperation("GetLanguagePackContent", new
                    {
                        EntityID = JDM.Int32(pack.EntityID)
                    });
                    cacheContent = new Dictionary<String, String>();
                    foreach(var i in content)
                        cacheContent.Add(i.Name, JDM.String(i.Value));
                    Context.HttpContext.Cache.Insert(cacheTimeKey, DateTime.UtcNow);
                    Context.HttpContext.Cache.Insert(cacheContentKey, cacheContent);
                }
                _languagePacks.Add(code, cacheContent);
            }
        }

        #endregion
    }
}